ARouter 解析

ARouter 解析

前言

ARouter 是阿里开源的一款 Android 端路由框架,在近几年掀起的组件化改造浪潮中异军突起,成为了最受开发者欢迎的组件化路由框架。

项目配置

项目结构

除去项目中的示例代码,ARouter 主要分为了四个模块

arouter-annotation

定义注解,包括常用的 Route、Autowired 等,注解的保留时长均为 RetentionPolicy.CLASS,意味着这些注解将会在编译期间通过注解处理器处理,与 butterknife 这个框架的原理类似

arouter-compiler

注解处理器,在 gradle 中通过 annotationProcessor 引入,在编译期间通过 Processor 处理对应的注解生成相应的类文件,在 Android Studio 中可以在 module 下 generatedJava 文件夹内可以看到生成的目标类文件

arouter-api

对外提供的核心 API,框架实际运行的逻辑代码,包括了对生成代码的反射调用

arouter-gradle-plugin

一个锦上添花的 gradle 插件,通过 gradle 的 Transform API 插入一些代码,可以优化 ARouter 初始化时比较多的一些遍历操作,这些都会在编辑期间生成的代码里完成

Compile

一般而言编译器注解会通过 annotationProcessor 生成所需的类,在运行时通过反射实例化,达到目标代码调用的目的。
ARouter 除了被 Deprecated 掉的 Param,还有 Autowired(类似于 Butterknife 的 BindView,自动化注入),Interceptor(用于路由跳转拦截)以及 Route(这个核心,用于标记需要跳转的 Activity,Fragment,Service),在 arouter-compiler 有这三个注解的注解处理器,以最重要的 RouteProcessor 为例

在 process 方法中收集所有的 Route.class 修饰的元素,进过 parseRoutes 方法生成需要的类,我们可以在 generatedJava 目录下查看,由于生成的代码过长且比较繁琐,所以就大致说明一下流程。

每个元素会生成一个 RouteMeta,根据元素路由路径中的 group 存放到 groupMap 全局变量中( Map<String, Set>,key 是 group 名称,value 这是元素的集合),接着会进行遍历时首先会根据 group 名生成 ARouter$$Group$${group} 类,遍历玩车后根据 module 名生成 ARouter$$Root$${module} 类作为 group 的引导类。同时,遍历时有注意收集 iProvider 的信息,遍历结束后会生成 ARouter$$Providers$${module} 类作为通过接口查找实例的容器

InterceptorProcessor 会生成包含所有 Interceptor 的 ARouter$$Interceptors$$app 类,AutowiredProcessor 则会在对应包目录下生成 {target}$$ARouter$$Autowired 类,在运行时通过 ARouter.getInstance().inject(this) 注入,原理类似于 butterknife

API

api 模块主要提供对外接口和逻辑实现,这里就主要介绍两个方法,一个是初始化 init 方法,还有一个则是跳转 navigation

ARouter.init

ARouter 是一个装饰类,具体的实现都是由 _ARouter 实现,_ARouter 的 init 方法中,除了 handler 之类的初始化,最重要的则是 LogisticsCenter 的初始化工作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
// excutor 可以供异步调用实现,比如说拦截器的代码执行
executor = tpe;

try {
long startInit = System.currentTimeMillis();

// 这个通过 gradle plugin 做了优化工作,下文会讲到
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
// 没有注册 gradle plugin 则会走这个初始化流程
Set<String> routerMap;

// 这里是通过分析整个 apk 文件获取 ARouter 生成的类, 这是一个非常耗时的操作,
// 所以这个会有个缓存策略,根据存储在本地的版本和版本号确定,测试环境的话则需要每次都重新生成
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// 这些 class 文件都是由 arouter-compiler 生成,遍历 apk 文件中的 dex 查找
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}

PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

// 将生成的类实例化,通过 loadInto 方法存入到 Warehouse 类里
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}

logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}

if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}

初始化的主要工作就是扫描安装包,查找生成 compile 生成的类,实例化之后存入到 warehouse 中。虽然做了缓存优化,还是存在着初次加载耗时的问题,所以后期也做了通过编译时代码插入的优化

Warehouse 类里面是一些静态变量,存储着一些路由缓存,以 groupsIndex 为例,ARouter 初始化时就会以 group 为单位存入生成的类,实际路由时才会加载对应的具体的路径 - RouteMeta 键值对

初始化完成之后会执行 afterInit,初始化 interceptorService 这个静态对象,它是所有的拦截器的根实现,过程类似于责任链的模式,可以追踪 /arouter/service/interceptor 这个路径查看实现类,有些可以注意的点,比如说 interceptorsIndex 是一个 TreeMap 可以根据 priority 排序,保证重要性高的先执行,比如说InterceptorServiceImpl.doInterceptions 方法中通过 CancelableCountDownLatch 实现了超时返回

ARouter.build 通过一个路径生成一个 Postcard 实例,Postcard 继承自 RouteMeta,会暂存一些参数等额外信息,通过调用 navigation 方法实现跳转,里面是的实现则是调用了
ARouter 中 Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) 这个方法

方法里的代码流程也是十分清晰的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// 预处理的服务,只要项目中存在实现该接口的类即可
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// Pretreatment failed, navigation canceled.
return null;
}

try {
// 这里是关键,现在的 postcard 只有路径信息,通过这个方法的处理,可以通过路径分析获取到额外的信息
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// 未获取到跳转目标的处理
logger.warning(Consts.TAG, ex.getMessage());

if (debuggable()) {
// Show friendly tips for user.
runInMainThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
});
}

if (null != callback) {
callback.onLost(postcard);
} else {
// No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}

return null;
}

if (null != callback) {
callback.onFound(postcard);
}

// 这里是拦截器的设置,由 interceptorService 实现拦截流程
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}

/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}

logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}

return null;
}

这个方法的关键是通过 LogisticsCenter.completion(postcard) 方法通过路径去获取 RouteMeta,这些 RouteMeta 由 compile 生成,注册到 Warehouse 里,在这里才会实例化加载进缓存中,以供这次及以后的调用,而后交由真正的 _navigation 方法执行路由操作,通过 postcard.getType() 获取到的类型执行真正的操作,activity 之类的会进行跳转,其他的会返回实例

Gradle Plugin

配置代码

1
2
3
4
5
6
7
8
9
10
11
12
apply plugin: 'com.alibaba.arouter'

buildscript {
repositories {
jcenter()
}

dependencies {
// Replace with the latest version
classpath "com.alibaba:arouter-register:?"
}
}

ARouter 的 init 方法最终会调用 LogisticsCenter 的 init 方法用来初始化,目的就是将生成的相关类实例化,将其中的界面跳转数据以及调用实例之类的存放到 Warehouse 对应的静态容器中,以供后来调转或者调用时查找。

在默认的实现中,会遍历 apk 文件中所有的 dex,查找所有的生成类,但这个过程不仅耗时,还存在不确定性的风险。对于一个全局性的框架来说是不可接受的。

这个插件就是为了解决这个问题而诞生的,Gradle Transform API 可以使工程在编译期间就能找到所有的生成类,然后将这些类实例化注册的代码插入到 LogisticsCenter 的 loadRouterMap 方法中,这个方法则是在 init 方法中会被调用,从而跳过分析 apk 文件来查询 ARouter 的生成类,达到相同的效果。

Gradle Transform API

Android 项目编译时,Gradle 提供了 Transform API 可以让开发者在编译时产生 Class 文件后,并在生成 Dex 之前做一些处理,Transform 会接收 inputs 输入(编译产生的Class文件), 并向已经产生的输入中添加一些东西替换掉这些输入产物,outputs 输出给下一个 Transform 处理,ProGuardTransform 就是系统实现的一个 Transform

ARouter gradlew plugin 的实现原理大致为:

在 AppExtension 里中注册 RegisterTransform 用于处理需求,在 transform 方法中,先遍历 jarInputs 和 directoryInputs(前者为工程 jar 包中的类,包括引用的 aouter-api,后者则为工程中的类),会找到 com/alibaba/android/arouter/core/LogisticsCenter.class 这个类,以及所有 com/alibaba/android/arouter/routes/ 包下的类,即我们生成的存放信息的类,通过 ScanClassVisitor 检查类继承的接口后分类存放。

收集完成后 registerList 会存放所有的相关类路径,遍历时通过 RegisterCodeGenerator 的 insertInitCodeTo 方法将注册方法代码写入到 LogisticsCenter 的 loadRouterMap 方法中,具体的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
void visitInsn(int opcode) {
//generate code before return
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
extension.classList.each { name ->
name = name.replaceAll("/", ".")
mv.visitLdcInsn(name)//类名
// generate invoke register method into LogisticsCenter.loadRouterMap()
mv.visitMethodInsn(Opcodes.INVOKESTATIC
, ScanSetting.GENERATE_TO_CLASS_NAME
, ScanSetting.REGISTER_METHOD_NAME
, "(Ljava/lang/String;)V"
, false)
}
}
super.visitInsn(opcode)
}

通过 ASM 的语法,将每个生成类都转换成 register(String className) 方法的调用,追踪一下不难发现,该方法做的是类实例化,存放路由信息,与上文利用 apk 查找类实例的结果一至,同时最后会将 registerByPlugin 标志置为 tru,使得框架初始化时跳过原上文的注册方式

总结

ARouter 的源码阅读并不太难,但是设计的十分优秀,而后又经过了数次迭代优化,行成了如今这个优秀的组件化路由框架